﻿using OA.Infrastructure.Extensions;

namespace AMS.Services.Impl;

public class FixedAssetsRepoService : IFixedAssetsRepoService
{
    private readonly DbContext _context;

    public FixedAssetsRepoService(DbContext context)
    {
        _context = context;
    }

    public async Task<PageResult<FixedAssetsRepo.QueryModel>> GetPageAsync(FixedAssetsRepo.ConditionSelectorCondition condition, CancellationToken cancellationToken)
    {
        var result = new PageResult<FixedAssetsRepo.QueryModel>();

        try
        {
            var expression = GetConditionSelectorCondition(condition);

            var fixedAssetsRepos = await _context.Set<FixedAssetsRepo>()
                .Include(x => x.Assets).ThenInclude(x => x.Category)
                .Include(x => x.Assets).ThenInclude(x => x.Supplier)
                .Include(x => x.Owner).ThenInclude(x => x.DirectLeader)
                .Include(x => x.Owner).ThenInclude(x => x.Department)
                .Include(x => x.Owner).ThenInclude(x => x.Roles)
                .Where(expression)
                .OrderBy(x => x.Id)
                .Skip((condition.PageNum - 1) * condition.PageSize)
                .Take(condition.PageSize)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            result.Data = fixedAssetsRepos.Select(x => GetQueryModel(x));

            result.PageNum = condition.PageNum;

            result.PageSize = condition.PageSize;

            result.Total = await _context.Set<FixedAssetsRepo>().CountAsync(expression, cancellationToken);

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    public async Task<OperationalResult> UpdateAsync(int id, FixedAssetsRepo.UpdateModel fixedAssetsRepo, CancellationToken cancellationToken)
    {
        var result = new OperationalResult();

        try
        {
            var entity = await _context.Set<FixedAssetsRepo>()
                .Where(m => m.Id.Equals(id))
                .FirstOrDefaultAsync(cancellationToken);

            if (entity is null)
            {
                result.Status = OperationalResult.FAILURE;

                result.Message = "0 rows affected.";

                return result;
            }

            entity.StorageLocation = fixedAssetsRepo.StorageLocation;

            _context.Set<FixedAssetsRepo>().Update(entity);

            var affectedRows = await _context.SaveChangesAsync(cancellationToken);

            result.Status = affectedRows > 0 ? OperationalResult.SUCCESS : OperationalResult.FAILURE;

            result.Message = $"{affectedRows} rows affected.";
        }
        catch (Exception error)
        {
            var exists = await _context.Set<FixedAssetsRepo>().AnyAsync(m => m.Id.Equals(id), cancellationToken);

            if (!exists)
            {
                result.Status = OperationalResult.FAILURE;

                result.Message = "0 rows affected.";
            }
            else
            {
                result.Status = OperationalResult.ERROR;

                result.Message = error.ToString();
            }
        }

        return result;
    }

    public async Task<ListResult<FixedAssetsRepo.QueryModel>> GetApprovedAsync(CancellationToken cancellationToken)
    {
        var result = new ListResult<FixedAssetsRepo.QueryModel>();

        try
        {
            var data = await _context.Set<FixedAssetsRepo>()
                .Include(x => x.Assets).ThenInclude(x => x.Category)
                .Include(x => x.Assets).ThenInclude(x => x.Supplier)
                .Where(n => n.Status == AssetsStatus.Normal && n.Owner == null)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            result.Data = data.Select(x => GetQueryModel(x));

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    public async Task<ListResult<FixedAssetsRepo.QueryModel>> GetOwnedAsync(CancellationToken cancellationToken)
    {
        var result = new ListResult<FixedAssetsRepo.QueryModel>();

        var currentUser = _context.CurrentUser();

        try
        {
            var data = await _context.Set<FixedAssetsRepo>()
                .Include(x => x.Assets).ThenInclude(x => x.Category)
                .Include(x => x.Assets).ThenInclude(x => x.Supplier)
                .Include(m => m.Owner)
                .Where(n => n.Owner != null && n.Owner.Id == currentUser.Id)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            result.Data = data.Select(x => GetQueryModel(x));

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    private static FixedAssetsRepo.QueryModel GetQueryModel(FixedAssetsRepo fixedAssetsRepo)
    {
        return new FixedAssetsRepo.QueryModel
        {
            Id = fixedAssetsRepo.Id,
            CreatedDate = fixedAssetsRepo.CreatedDate,
            LastModifiedDate = fixedAssetsRepo.LastModifiedDate,
            Assets = FixedAssetsService.GetQueryModel(fixedAssetsRepo.Assets),
            Owner = fixedAssetsRepo.Owner is not null ? DefinitionServiceExtensions.GetUserQueryModel(fixedAssetsRepo.Owner) : null,
            StorageLocation = fixedAssetsRepo.StorageLocation,
            UseLocation = fixedAssetsRepo.UseLocation,
            PurchaseDate = fixedAssetsRepo.PurchaseDate,
            EntryDate = fixedAssetsRepo.EntryDate,
            Status = fixedAssetsRepo.Status,
        };
    }

    private static Expression<Func<FixedAssetsRepo, bool>> GetConditionSelectorCondition(FixedAssetsRepo.ConditionSelectorCondition condition)
    {
        Expression<Func<FixedAssetsRepo, bool>>? expression = null;

        if (condition.Keywords is not null)
        {
            Expression<Func<FixedAssetsRepo, bool>>? keywords = null;
            keywords = fixedAssetsRepo => fixedAssetsRepo.Assets.Name.Contains(condition.Keywords);
            keywords = keywords.Or(fixedAssetsRepo => fixedAssetsRepo.Assets.Specs.Contains(condition.Keywords));
            keywords = keywords.Or(fixedAssetsRepo => fixedAssetsRepo.Assets.Supplier.Name.Contains(condition.Keywords));
            keywords = keywords.Or(fixedAssetsRepo => fixedAssetsRepo.Owner.Name.Contains(condition.Keywords));
            keywords = keywords.Or(fixedAssetsRepo => fixedAssetsRepo.StorageLocation.Contains(condition.Keywords));
            keywords = keywords.Or(fixedAssetsRepo => fixedAssetsRepo.UseLocation.Contains(condition.Keywords));
            expression = expression?.And(keywords) ?? keywords;
        }

        if (condition.StatusSelector is not null)
        {
            Expression<Func<FixedAssetsRepo, bool>>? statusSelector = null;
            statusSelector = fixedAssetsRepo => condition.StatusSelector == fixedAssetsRepo.Status;
            expression = expression?.And(statusSelector) ?? statusSelector;
        }

        return expression ?? (fixedAssetsRepo => true);
    }
}